﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Exceptions;
using OverStore.Runtime.Storage;
using OverStore.Runtime.ObjectRepository;

namespace OverStore.Runtime.ObjectRepository
{
    /// <summary>
    /// Redirects persisting of <typeparamref name="TInstance"/> to repository of type <typeparamref name="TRedirectToInstance"/>.
    /// </summary>
    public class RedirectObjectRepository<TInstance, TIdentifier, TRedirectToInstance, TRedirectToIdentifier> : IEntityRegistry<TInstance, TIdentifier>
        where TInstance : class
        where TRedirectToInstance : class, TInstance
        where TRedirectToIdentifier : TIdentifier
    {
        private readonly IPersistenceServiceProvider _Provider;

        /// <summary>
        /// Initializes a new instance of the <see cref="RedirectObjectRepository&lt;TInstance, TIdentifier, TRedirectToInstance, TRedirectToIdentifier&gt;"/> class.
        /// </summary>
        public RedirectObjectRepository(IPersistenceServiceProvider provider)
        {
            if (Object.ReferenceEquals(provider, null))
                throw new ArgumentNullException("provider");
            _Provider = provider;
        }

        /// <summary>
        /// Get identifier for object registered in session.
        /// If object is not registered in session, it will be placed in session.
        /// If object does not have identifier yet, the identifier will be assigned to it
        /// corresponding to identification rules.
        /// </summary>
        /// <param name="instance">The instance to get identifier.</param>
        /// <returns>The instance identifier.</returns>
        /// <exception cref="PersistingRuntimeException">Error occured during get identifier.</exception>
        public TIdentifier GetIdentifier(TInstance instance)
        {
            return (TIdentifier)_Provider.GetIdentifier<TRedirectToInstance, TRedirectToIdentifier>((TRedirectToInstance)instance);
        }

        /// <summary>
        /// Remember new identifier for instance registered in session.
        /// Call AcceptChanges method to actually apply new identifier to instance and
        /// register new identifier in session.
        /// </summary>
        /// <param name="instance">The instance to update its identifier.</param>
        /// <param name="newIdentifier">The new identifier.</param>
        /// <exception cref="PersistingRuntimeException">Error occured during update identifier.</exception>
        /// <remarks>
        /// Identifier updating is required if identifiers are generated by database.
        /// Delayed identifier updating is required because if error occured due updating the instance
        /// or other instance in batch, identifier value generated by database becomes invalid.
        /// </remarks>
        public void PutNewIdentifier(TInstance instance, TIdentifier newIdentifier)
        {
            _Provider.PutNewIdentifier<TRedirectToInstance, TRedirectToIdentifier>((TRedirectToInstance)instance, (TRedirectToIdentifier)newIdentifier);
        }

        /// <summary>
        /// Get object registered in session.
        /// If object is not found, the object will be constructed, registered in session, reloaded and
        /// returned.
        /// If object does not registered in session, it will be constructed
        /// using data extracted from default gateway.
        /// </summary>
        /// <param name="identifier">The identifier of instance to get.</param>
        /// <returns>The instance with specified identifier.</returns>
        /// <exception cref="PersistingRuntimeException">Error occured during get object by identifier.</exception>
        public TInstance GetInstance(TIdentifier identifier)
        {
            return (TInstance)_Provider.GetInstance<TRedirectToInstance, TRedirectToIdentifier>((TRedirectToIdentifier)identifier);
        }

        /// <summary>
        /// Gets entity with specified identifier. If entity registered in session it will be returned.
        /// Otherwise it will loaded from data storage. If there is no entity with specified identifier in data storage,
        /// method returns default value of <typeparamref name="TInstance"/> type.
        /// </summary>
        /// <param name="identifier">The identifier of entity to get from registry.</param>
        /// <returns>
        /// The entity with specified identifier or default value of <typeparamref name="TEntity"/> type of there is no
        /// entity with specified identifier exists in data storage.
        /// </returns>
        public TInstance GetInstanceOrDefault(TIdentifier identifier)
        {
            return (TInstance)_Provider.GetInstanceOrDefault<TRedirectToInstance, TRedirectToIdentifier>((TRedirectToIdentifier)identifier);
        }

        /// <summary>
        /// Get object registered in session.
        /// If object is not found, the object will be constructed, registered in session and reloaded
        /// using data passed as <paramref name="data"/> argument.
        /// If <paramref name="data"/> is null, the object will be constructed using data extracted from default gateway.
        /// </summary>
        /// <param name="identifier">The identifier of instance to get.</param>
        /// <param name="data">The data used for object construction or null for using data from default gateway.</param>
        /// <returns>The instance with specified identifier.</returns>
        /// <exception cref="PersistingRuntimeException">Error occured during get object by identifier.</exception>
        public TInstance GetInstance(TIdentifier identifier, TypedRecord data)
        {
            return (TInstance)_Provider.GetInstance<TRedirectToInstance, TRedirectToIdentifier>((TRedirectToIdentifier)identifier, data);
        }

        /// <summary>
        /// 	<para> Updates data of specified instance using data extracted from data storage,
        /// and reloads instance's aggregates and marks instance as unchanged,
        /// so further saving will not cause data storage updating until object will be changed.
        /// Method returns <c>true</c> if instance was successfuly reloaded, <c>false</c> if
        /// data storage commands used for reloading instance does not return any data.
        /// </para>
        /// 	<para> If specified <paramref name="instance"/> is not registered in repository, method also registers it.
        /// If deffer loading is supported and enabled, the object will only be registered for
        /// loading, and loading itself will be performed by request.</para>
        /// 	<para>
        /// If repository is configured for deferred loading, method immediately returns <c>true</c>,
        /// and throws <see cref="PersistingRuntimeException"/> if reloading will fails.
        /// </para>
        /// </summary>
        /// <param name="instance">The instance to reload.</param>
        /// <returns></returns>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with same  already registered.</exception>
        /// <exception cref="PersistingRuntimeException">Error is occured during reload object data.</exception>
        public bool Reload(TInstance instance)
        {
            return _Provider.Reload<TRedirectToInstance>((TRedirectToInstance)instance);
        }

        /// <summary>
        /// 	<para>
        /// Saves changes made in <paramref name="instance"/> in data storage and invokes saving instance's aggregates.
        /// If instance was previously marked as deleted, method removes it from data storage.
        /// </para>
        /// 	<para>
        /// If instance supports deffer loading and is not loaded yet, method does not perform any
        /// actions, because changes can not be made without instance loading.
        /// </para>
        /// 	<para>
        /// Method does not accept instance changes, but types implements current method should consider possibility
        /// of calling this method few times with same instance, in case of cycle references, for example.
        /// Repeated calls should not cause repeated saving, so method should take steps to avoid repeated saving.
        /// For example, instance may internally maintain list of saved instances, checks this list on saving and
        /// clear this list in see cref="IRepository{TInstance}.AcceptChanges" method.
        /// </para>
        /// </summary>
        /// <param name="instance">The instance to save changes.</param>
        /// <exception cref="PersistingRuntimeException">Error occured while save object.</exception>
        /// <exception cref="VersionConflictException">Version conflict occured during save object.</exception>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with same identifier already registered.</exception>
        /// <exception cref="PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        public void Save(TInstance instance)
        {
            _Provider.Save<TRedirectToInstance>((TRedirectToInstance)instance);
        }

        /// <summary>
        /// Mark <paramref name="instance"/> as deleted.
        /// </summary>
        /// <param name="instance">The instance to mark as deleted.</param>
        /// <remarks>
        /// Method itself does not save any changes to storage. For actually delete <paramref name="instance"/>
        /// from storage, call <see cref="IRepository{TInstance}.Save"/> method.
        /// </remarks>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with same identifier already registered.</exception>
        /// <exception cref="PersistingRuntimeException">Error occured while mark object as deleted.</exception>
        /// <exception cref="PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        public void MarkAsDeleted(TInstance instance)
        {
            _Provider.MarkAsDeleted<TRedirectToInstance>((TRedirectToInstance)instance);
        }

        /// <summary>
        /// Marks <paramref name="instance"/> as needed to be physically saved to storage even
        /// if <paramref name="instance"/> data are not changed.
        /// This method may be called any times untill AcceptChanges method called, but instance will be saved only once.
        /// </summary>
        /// <param name="instance">Instance to mark as dirty.</param>
        /// <remarks>
        /// Use this method in couple with optimistic locking (version checking) to organize object hierarchy into 'atom' group.
        /// Call this method on successful instance saving using other members of 'atom' object group, then save these objects.
        /// </remarks>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with the same identifier already registered.</exception>
        /// <exception cref="PersistingRuntimeException">Error occured while mark object as dirty.</exception>
        /// <exception cref="PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        public void MarkAsDirty(TInstance instance)
        {
            _Provider.MarkAsDirty<TRedirectToInstance>((TRedirectToInstance)instance);
        }

        /// <summary>
        /// 	<para> Registers <paramref name="instance"/> in repository, if it is not registered yet. After registration
        /// <paramref name="instance"/> become available by its identifier. </para>
        /// 	<para> If same instance already registered in repository, method does not perform any action.</para>
        /// </summary>
        /// <param name="instance">The instance to registers in repository.</param>
        /// <exception cref="IdentityReferenceIntegrityException">Different instance with same identifier already registered.</exception>
        /// <exception cref="PersistingRuntimeException">Error occured during add object.</exception>
        /// <exception cref="PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        public void Add(TInstance instance)
        {
            _Provider.Add<TRedirectToInstance>((TRedirectToInstance)instance);
        }

        /// <summary>
        /// Joins specified instance or collection to repository if required, applies changes made to <paramref name="instance"/> and attach aggregates of specified instance.
        /// If specified instance is collection, attach each items and deletes items deleted from collection since detaching.
        /// </summary>
        /// <param name="instance">The instance to attach.</param>
        /// <returns>
        /// The instance registered in repository contains data of <paramref name="instance"/>.
        /// </returns>
        /// <exception cref="PersistingRuntimeException">Error occured during Attach object.</exception>
        /// <exception cref="PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        public TInstance Attach(TInstance instance)
        {
            return (TInstance)_Provider.Attach<TRedirectToInstance>((TRedirectToInstance)instance);
        }

        /// <summary>
        /// Get type of objects served by repository.
        /// </summary>
        public Type Type
        {
            get
            {
                return typeof(TInstance);
            }
        }

        /// <summary>
        /// Save changes made to all objects registered in repository.
        /// </summary>
        /// <exception cref="T:OverStore.Exceptions.PersistenceOperationIsNotSupportedException">Operation is not supported because of configuration part responsible for this operation is missing or does not support specific functionallity.</exception>
        public void SaveAll()
        {
        }

        /// <summary>
        /// Executes the object reader.
        /// </summary>
        /// <typeparam name="TParameter">The type of the parameter.</typeparam>
        /// <param name="parameter">The parameter.</param>
        /// <returns></returns>
        public IEnumerable<TInstance> ExecuteObjectReader<TParameter>(TParameter parameter)
            where TParameter :class
        {
            return _Provider.ExecuteObjectReader<TRedirectToInstance, TParameter>(parameter).Cast<TInstance>();
        }

        /// <summary>
        /// Return enumerator throught object set.
        /// </summary>
        /// <returns>
        /// The sequence of object read from data storage.
        /// </returns>
        /// <exception cref="PersistingRuntimeException">Error occured while use object reader.</exception>
        public IEnumerable<TInstance> ExecuteObjectReader()
        {
            return _Provider.ExecuteObjectReader<TRedirectToInstance>().Cast<TInstance>();
        }
    }
}
